<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Go by Example 中文版: 状态协程</title>
    <link rel=stylesheet href="site.css">
  </head>
  <script>
      onkeydown = (e) => {
          
          if (e.key == "ArrowLeft") {
              window.location.href = 'mutexes';
          }
          
          
          if (e.key == "ArrowRight") {
              window.location.href = 'sorting';
          }
          
      }
  </script>
  <body>

    <div class="example" id="stateful-goroutines">
      <h2><a href="./">Go by Example 中文版</a>: 状态协程</h2>
      
      <table>
        
        <tr>
          <td class="docs">
            <p>在前面的例子中，我们用 <a href="mutexes">互斥锁</a> 进行了明确的锁定，
来让共享的 state 跨多个 Go 协程同步访问。
另一个选择是，使用内建协程和通道的同步特性来达到同样的效果。
Go 共享内存的思想是，通过通信使每个数据仅被单个协程所拥有，即通过通信实现共享内存。
基于通道的方法与该思想完全一致！</p>

          </td>
          <td class="code empty leading">
            
          
          </td>
        </tr>
        
        <tr>
          <td class="docs">
            
          </td>
          <td class="code leading">
            <a href="http://play.golang.org/p/8NGFbhtE1ck"><img title="Run code" src="play.png" class="run" /></a><img title="Copy code" src="clipboard.png" class="copy" />
          <div class="highlight"><pre><span class="kn">package</span> <span class="nx">main</span>
</pre></div>

          </td>
        </tr>
        
        <tr>
          <td class="docs">
            
          </td>
          <td class="code leading">
            
          <div class="highlight"><pre><span class="kn">import</span> <span class="p">(</span>
    <span class="s">&quot;fmt&quot;</span>
    <span class="s">&quot;math/rand&quot;</span>
    <span class="s">&quot;sync/atomic&quot;</span>
    <span class="s">&quot;time&quot;</span>
<span class="p">)</span>
</pre></div>

          </td>
        </tr>
        
        <tr>
          <td class="docs">
            <p>在这个例子中，state 将被一个单独的协程拥有。
这能保证数据在并行读取时不会混乱。
为了对 state 进行读取或者写入，
其它的协程将发送一条数据到目前拥有数据的协程中，
然后等待接收对应的回复。
结构体 <code>readOp</code> 和 <code>writeOp</code> 封装了这些请求，并提供了响应协程的方法。</p>

          </td>
          <td class="code leading">
            
          <div class="highlight"><pre><span class="kd">type</span> <span class="nx">readOp</span> <span class="kd">struct</span> <span class="p">{</span>
    <span class="nx">key</span>  <span class="kt">int</span>
    <span class="nx">resp</span> <span class="kd">chan</span> <span class="kt">int</span>
<span class="p">}</span>
<span class="kd">type</span> <span class="nx">writeOp</span> <span class="kd">struct</span> <span class="p">{</span>
    <span class="nx">key</span>  <span class="kt">int</span>
    <span class="nx">val</span>  <span class="kt">int</span>
    <span class="nx">resp</span> <span class="kd">chan</span> <span class="kt">bool</span>
<span class="p">}</span>
</pre></div>

          </td>
        </tr>
        
        <tr>
          <td class="docs">
            
          </td>
          <td class="code leading">
            
          <div class="highlight"><pre><span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
</pre></div>

          </td>
        </tr>
        
        <tr>
          <td class="docs">
            <p>和前面的例子一样，我们会计算操作执行的次数。</p>

          </td>
          <td class="code leading">
            
          <div class="highlight"><pre>    <span class="kd">var</span> <span class="nx">readOps</span> <span class="kt">uint64</span>
    <span class="kd">var</span> <span class="nx">writeOps</span> <span class="kt">uint64</span>
</pre></div>

          </td>
        </tr>
        
        <tr>
          <td class="docs">
            <p>其他协程将通过 <code>reads</code> 和 <code>writes</code> 通道来发布 <code>读</code> 和 <code>写</code> 请求。</p>

          </td>
          <td class="code leading">
            
          <div class="highlight"><pre>    <span class="nx">reads</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">chan</span> <span class="nx">readOp</span><span class="p">)</span>
    <span class="nx">writes</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">chan</span> <span class="nx">writeOp</span><span class="p">)</span>
</pre></div>

          </td>
        </tr>
        
        <tr>
          <td class="docs">
            <p>这就是拥有 <code>state</code> 的那个协程，
和前面例子中的 map 一样，不过这里的 state 是被这个状态协程私有的。
这个协程不断地在 <code>reads</code> 和 <code>writes</code> 通道上进行选择，并在请求到达时做出响应。
首先，执行请求的操作；然后，执行响应，在响应通道 <code>resp</code> 上发送一个值，表明请求成功（<code>reads</code> 的值则为 state 对应的值）。</p>

          </td>
          <td class="code leading">
            
          <div class="highlight"><pre>    <span class="k">go</span> <span class="kd">func</span><span class="p">()</span> <span class="p">{</span>
        <span class="kd">var</span> <span class="nx">state</span> <span class="p">=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">int</span><span class="p">]</span><span class="kt">int</span><span class="p">)</span>
        <span class="k">for</span> <span class="p">{</span>
            <span class="k">select</span> <span class="p">{</span>
            <span class="k">case</span> <span class="nx">read</span> <span class="o">:=</span> <span class="o">&lt;-</span><span class="nx">reads</span><span class="p">:</span>
                <span class="nx">read</span><span class="p">.</span><span class="nx">resp</span> <span class="o">&lt;-</span> <span class="nx">state</span><span class="p">[</span><span class="nx">read</span><span class="p">.</span><span class="nx">key</span><span class="p">]</span>
            <span class="k">case</span> <span class="nx">write</span> <span class="o">:=</span> <span class="o">&lt;-</span><span class="nx">writes</span><span class="p">:</span>
                <span class="nx">state</span><span class="p">[</span><span class="nx">write</span><span class="p">.</span><span class="nx">key</span><span class="p">]</span> <span class="p">=</span> <span class="nx">write</span><span class="p">.</span><span class="nx">val</span>
                <span class="nx">write</span><span class="p">.</span><span class="nx">resp</span> <span class="o">&lt;-</span> <span class="kc">true</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}()</span>
</pre></div>

          </td>
        </tr>
        
        <tr>
          <td class="docs">
            <p>启动 100 个协程通过 <code>reads</code> 通道向拥有 state 的协程发起读取请求。
每个读取请求需要构造一个 <code>readOp</code>，发送它到 <code>reads</code> 通道中，
并通过给定的 <code>resp</code> 通道接收结果。</p>

          </td>
          <td class="code leading">
            
          <div class="highlight"><pre>    <span class="k">for</span> <span class="nx">r</span> <span class="o">:=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">r</span> <span class="p">&lt;</span> <span class="mi">100</span><span class="p">;</span> <span class="nx">r</span><span class="o">++</span> <span class="p">{</span>
        <span class="k">go</span> <span class="kd">func</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">for</span> <span class="p">{</span>
                <span class="nx">read</span> <span class="o">:=</span> <span class="nx">readOp</span><span class="p">{</span>
                    <span class="nx">key</span><span class="p">:</span>  <span class="nx">rand</span><span class="p">.</span><span class="nx">Intn</span><span class="p">(</span><span class="mi">5</span><span class="p">),</span>
                    <span class="nx">resp</span><span class="p">:</span> <span class="nb">make</span><span class="p">(</span><span class="kd">chan</span> <span class="kt">int</span><span class="p">)}</span>
                <span class="nx">reads</span> <span class="o">&lt;-</span> <span class="nx">read</span>
                <span class="o">&lt;-</span><span class="nx">read</span><span class="p">.</span><span class="nx">resp</span>
                <span class="nx">atomic</span><span class="p">.</span><span class="nx">AddUint64</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">readOps</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
                <span class="nx">time</span><span class="p">.</span><span class="nx">Sleep</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}()</span>
    <span class="p">}</span>
</pre></div>

          </td>
        </tr>
        
        <tr>
          <td class="docs">
            <p>用相同的方法启动 10 个写操作。</p>

          </td>
          <td class="code leading">
            
          <div class="highlight"><pre>    <span class="k">for</span> <span class="nx">w</span> <span class="o">:=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">w</span> <span class="p">&lt;</span> <span class="mi">10</span><span class="p">;</span> <span class="nx">w</span><span class="o">++</span> <span class="p">{</span>
        <span class="k">go</span> <span class="kd">func</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">for</span> <span class="p">{</span>
                <span class="nx">write</span> <span class="o">:=</span> <span class="nx">writeOp</span><span class="p">{</span>
                    <span class="nx">key</span><span class="p">:</span>  <span class="nx">rand</span><span class="p">.</span><span class="nx">Intn</span><span class="p">(</span><span class="mi">5</span><span class="p">),</span>
                    <span class="nx">val</span><span class="p">:</span>  <span class="nx">rand</span><span class="p">.</span><span class="nx">Intn</span><span class="p">(</span><span class="mi">100</span><span class="p">),</span>
                    <span class="nx">resp</span><span class="p">:</span> <span class="nb">make</span><span class="p">(</span><span class="kd">chan</span> <span class="kt">bool</span><span class="p">)}</span>
                <span class="nx">writes</span> <span class="o">&lt;-</span> <span class="nx">write</span>
                <span class="o">&lt;-</span><span class="nx">write</span><span class="p">.</span><span class="nx">resp</span>
                <span class="nx">atomic</span><span class="p">.</span><span class="nx">AddUint64</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">writeOps</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
                <span class="nx">time</span><span class="p">.</span><span class="nx">Sleep</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}()</span>
    <span class="p">}</span>
</pre></div>

          </td>
        </tr>
        
        <tr>
          <td class="docs">
            <p>让协程们跑 1s。</p>

          </td>
          <td class="code leading">
            
          <div class="highlight"><pre>    <span class="nx">time</span><span class="p">.</span><span class="nx">Sleep</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span>
</pre></div>

          </td>
        </tr>
        
        <tr>
          <td class="docs">
            <p>最后，获取并报告 <code>ops</code> 值。</p>

          </td>
          <td class="code">
            
          <div class="highlight"><pre>    <span class="nx">readOpsFinal</span> <span class="o">:=</span> <span class="nx">atomic</span><span class="p">.</span><span class="nx">LoadUint64</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">readOps</span><span class="p">)</span>
    <span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;readOps:&quot;</span><span class="p">,</span> <span class="nx">readOpsFinal</span><span class="p">)</span>
    <span class="nx">writeOpsFinal</span> <span class="o">:=</span> <span class="nx">atomic</span><span class="p">.</span><span class="nx">LoadUint64</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">writeOps</span><span class="p">)</span>
    <span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;writeOps:&quot;</span><span class="p">,</span> <span class="nx">writeOpsFinal</span><span class="p">)</span>
<span class="p">}</span>
</pre></div>

          </td>
        </tr>
        
      </table>
      
      <table>
        
        <tr>
          <td class="docs">
            <p>运行这个程序显示这个基于协程的状态管理的例子
达到了每秒大约 80,000 次操作。</p>

          </td>
          <td class="code leading">
            
          <div class="highlight"><pre><span class="gp">$</span> go run stateful-goroutines.go
<span class="go">readOps: 71708</span>
<span class="go">writeOps: 7177</span>
</pre></div>

          </td>
        </tr>
        
        <tr>
          <td class="docs">
            <p>通过这个例子我们可以看到，基于协程的方法比基于互斥锁的方法要复杂得多。
但是，在某些情况下它可能很有用，
例如，当你涉及其他通道，或者管理多个同类互斥锁时，会很容易出错。
您应该使用最自然的方法，尤其是在理解程序正确性方面。</p>

          </td>
          <td class="code empty">
            
          
          </td>
        </tr>
        
      </table>
      
      
      <p class="next">
        下一个例子: <a href="sorting">排序</a>
      </p>
      
      <p class="footer">
        <a href="https://twitter.com/mmcgrana">@mmcgrana</a> 编写 | <a href="https://github.com/gobyexample-cn">gobyexample-cn</a> 翻译 | <a href="https://github.com/gobyexample-cn/gobyexample/issues">反馈</a> | <a href="https://github.com/gobyexample-cn/gobyexample">源码</a> | <a href="https://github.com/mmcgrana/gobyexample#license">license</a>      </p>
      </p>
    </div>
    <script>
      var codeLines = [];
      codeLines.push('');codeLines.push('package main\u000A');codeLines.push('import (\u000A    \"fmt\"\u000A    \"math/rand\"\u000A    \"sync/atomic\"\u000A    \"time\"\u000A)\u000A');codeLines.push('type readOp struct {\u000A    key  int\u000A    resp chan int\u000A}\u000Atype writeOp struct {\u000A    key  int\u000A    val  int\u000A    resp chan bool\u000A}\u000A');codeLines.push('func main() {\u000A');codeLines.push('    var readOps uint64\u000A    var writeOps uint64\u000A');codeLines.push('    reads := make(chan readOp)\u000A    writes := make(chan writeOp)\u000A');codeLines.push('    go func() {\u000A        var state = make(map[int]int)\u000A        for {\u000A            select {\u000A            case read := \x3C-reads:\u000A                read.resp \x3C- state[read.key]\u000A            case write := \x3C-writes:\u000A                state[write.key] = write.val\u000A                write.resp \x3C- true\u000A            }\u000A        }\u000A    }()\u000A');codeLines.push('    for r := 0; r \x3C 100; r++ {\u000A        go func() {\u000A            for {\u000A                read := readOp{\u000A                    key:  rand.Intn(5),\u000A                    resp: make(chan int)}\u000A                reads \x3C- read\u000A                \x3C-read.resp\u000A                atomic.AddUint64(&readOps, 1)\u000A                time.Sleep(time.Millisecond)\u000A            }\u000A        }()\u000A    }\u000A');codeLines.push('    for w := 0; w \x3C 10; w++ {\u000A        go func() {\u000A            for {\u000A                write := writeOp{\u000A                    key:  rand.Intn(5),\u000A                    val:  rand.Intn(100),\u000A                    resp: make(chan bool)}\u000A                writes \x3C- write\u000A                \x3C-write.resp\u000A                atomic.AddUint64(&writeOps, 1)\u000A                time.Sleep(time.Millisecond)\u000A            }\u000A        }()\u000A    }\u000A');codeLines.push('    time.Sleep(time.Second)\u000A');codeLines.push('    readOpsFinal := atomic.LoadUint64(&readOps)\u000A    fmt.Println(\"readOps:\", readOpsFinal)\u000A    writeOpsFinal := atomic.LoadUint64(&writeOps)\u000A    fmt.Println(\"writeOps:\", writeOpsFinal)\u000A}\u000A');codeLines.push('');codeLines.push('');
    </script>
    <script src="site.js" async></script>
  </body>
</html>
